# This file is part of Appartition, a lightweight and portable backup tool.
# Copyright (C) 2012 Simon Grieger
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

###########################################################################
# Package: Appartition::Shell
#
# This package implements the user interface.
###########################################################################

package Appartition::Shell;

use 5.006;
use strict;
use warnings;

use Getopt::Long;

use Appartition;
use Appartition::Version;

###########################################################################
# Group: Functions for User Interaction
###########################################################################

# Function: parse_command_line
#
# Parses the command line. Validates the options and arguments that are not
# validated by <Appartition::Core> later on, and handles some low-level
# options, such as "--version".
#
# Returns:
#
#   True.
sub parse_command_line #()
  {
    parse_options();
    handle_basic_options();
    validate_command_line();

    return 1;
  }

###########################################################################
# Group: Output Functions
###########################################################################

# Function: show_notification
#
# Prints a notification to STDERR. A line break will be appended, if not
# already present.
#
# Parameters:
#
#   text - Message text (a list of strings). The message may contain line
#          breaks.
#
# Returns:
#
#   True.
sub show_notification #(text)
  {
    print STDERR format_output(' * ', @_);
    return 1;
  }

# Function: show_progress
#
# Prints a progress notification to STDERR. A line break will be appended,
# if not already present.
#
# Parameters:
#
#   text - Message text (a list of strings). The message may contain line
#          breaks.
#
# Returns:
#
#   True.
sub show_progress #(text)
  {
    # Remove all trailing whitespace so that we can append an ellipsis.
    my $text = join_text(@_);

    show_notification($text, ' ...');
    return 1;
  }

# Function: show_error
#
# Prints an error message to STDERR. A line break will be appended, if not
# already present. This function may be used to show errors from caught
# exceptions.
#
# Parameters:
#
#   text - Message text (a list of strings). The message may contain line
#          breaks.
#
# Returns:
#
#   True.
sub show_error #(text)
  {
    print STDERR format_output('Error: ', @_);
    return 1;
  }

# Function: show_warning
#
# Prints a warning to STDERR. A line break will be appended, if not already
# present.
#
# Parameters:
#
#   text - Message text (a list of strings). The message may contain line
#          breaks.
#
# Returns:
#
#   True.
sub show_warning #(text)
  {
    print STDERR format_output('Warning: ', @_);
    return 1;
  }

# Function: show_debug_output
#
# Prints a debugging message to STDERR if debugging is currently enabled. A
# line break will be appended, if not already present.
#
# Parameters:
#
#   text - Message text (a list of strings). The message may contain line
#          breaks.
#
# Returns:
#
#   True.
sub show_debug_output #(text)
  {
    print STDERR format_output('Debug: ', @_)
      if ($Appartition::OPTION_VALUES->{'debug'} or $ENV{'DEBUG'});
    return 1;
  }

###########################################################################
# Group: Formatting Functions
###########################################################################

# Function: format_output
#
# Formats text for output. Prepends a prefix to the first line of the given
# message text and indents the following lines so that they start at the
# same column as the first one. A line break will be appended, if not
# already present.
#
# Parameters:
#
#   prefix - Prefix (see description).
#   text   - Message text (a list of strings). The message may contain line
#            breaks.
#
# Returns:
#
#   The formatted output as a single string.
sub format_output #(prefix, text)
  {
    my $prefix = shift;
    my $indent = ' ' x length($prefix);
    my $output = $prefix . join_text(@_);

    $output =~ s/\n/\n$indent/og;
    $output .= "\n";

    # TODO: Split lines longer than 75 characters?

    return $output;
  }

# Function: join_text
#
# Joins the given message text into one string. Removes all trailing line
# breaks and whitespace.
#
# Parameters:
#
#   text - Message text (a list of strings). The message may contain line
#          breaks.
#
# Returns:
#
#   The given text as a single string.
sub join_text #(text)
  {
    my $text = join('', @_);
    $text =~ s/\s+$//o;
    return $text;
  }

###########################################################################
# Group: Miscellaneous Functions
###########################################################################

# Function: say_hello
#
# Shows version and copyright information.
#
# Returns:
#
#   True.
sub say_hello #()
  {
    print STDERR Appartition::Version::get_version(), "\n";
    print STDERR Appartition::Version::get_copyright(), "\n\n";

    return 1;
  }

# Function: say_goodbye
#
# Shows a final notification indicating success or failure of the program.
#
# Parameters:
#
#   success - True to indicate success, false to indicate failure.
#
# Returns:
#
#   Zero on success, a value greater than zero on failure, where success or
#   failure is determined by the given parameter. The return value may be
#   used as the program's exit status.
sub say_goodbye #(success)
  {
    my ($success) = @_;
    my ($mouth, $rv) = $success ? (')', 0) : ('(', 1);

    show_notification('Goodbye :-' . $mouth);
    return $rv;
  }

###########################################################################
# Group: Internal Functions
#
# The functions in this group are not meant to be called directly. They are
# used internally by this package's functions.
###########################################################################

# Function: parse_options
#
# Parses the options on the command line.
#
# Returns:
#
#   True.
sub parse_options #()
  {
    Getopt::Long::Configure('bundling', 'no_ignore_case');
    GetOptions($Appartition::OPTION_VALUES, @Appartition::OPTION_SPECS);

    return 1;
  }

# Function: handle_basic_options
#
# Handles some low-level options, such as "--version".
#
# Returns:
#
#   True.
sub handle_basic_options #()
  {
    # Show help if requested.
    if ($Appartition::OPTION_VALUES->{'help'}) {
      # TODO: Show help.
      exit(0);
    }

    # Show usage if requested.
    if ($Appartition::OPTION_VALUES->{'usage'}) {
      # TODO: Show usage.
      exit(0);
    }

    # Show version information if requested.
    if ($Appartition::OPTION_VALUES->{'version'}) {
      # TODO: Show licensing information.
      exit(0);
    }

    return 1;
  }

# Function: validate_command_line
#
# Validates the options and arguments that are not validated by
# <Appartition::Core> later on.
#
# Returns:
#
#   True.
sub validate_command_line #()
  {
    # Validate the command line arguments.
    unless (@ARGV == 1) {
      show_error('You need to specify a destination') if (@ARGV < 1);
      # TODO: Show usage.
      exit(1);
    }

    # Check whether the specified destination exists and is a directory.
    unless (-d $ARGV[0]) {
      show_error('"' . $ARGV[0] . '" does not exist or is not a directory');
      # TODO: Show usage.
      exit(1);
    }

    # Check whether the specified workspace exists and is a directory.
    if (defined $Appartition::OPTION_VALUES->{'workspace'} and
        not -d $Appartition::OPTION_VALUES->{'workspace'}) {
      show_error('"' . $Appartition::OPTION_VALUES->{'workspace'} . '" ' .
                 'does not exist or is not a directory');
      exit(1);
    }

    # Validate the *-command options. Their values must contain at least
    # one non-whitespace character.
    foreach (keys %$Appartition::OPTION_VALUES) {
      next unless (m'\-command$'o);

      unless ($Appartition::OPTION_VALUES->{$_} =~ m'\S'o) {
        die 'Invalid value to option "--' . $_ . '"' . "\n";
      }
    }

    return 1;
  }

1;
